﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GASS.CUDA;
using GASS.CUDA.Types;
using System.IO;
using GASS.CUDA.Direct3D;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Visualizator.Physics
{
    /// <summary>
    /// El integrador Verlet que corre el algoritmo completamente
    /// en el CPU.
    /// </summary>
    public class GPUVerletIntegrator: IVerletIntegrator
    {
        private Float4[] pos;
        private Float4[] prevPos;

        public Float4[] Pos
        {
            get { return pos; }
            set { pos = value; d_Pos = cuda.CopyHostToDevice<Float4>(value); Util.CheckCUDA(); }
        }

        public Float4[] PrevPos
        {
            get { return prevPos; }
            set { prevPos = value; d_PrevPos = cuda.CopyHostToDevice<Float4>(value); Util.CheckCUDA(); }
        }

        public int ParticleCount { get; set; }

        public GPUVerletIntegrator()
        {
            // Init and select 1st device.
            //cudaError err = CUD3DRuntime.cudaD3D9SetDirect3DDevice(Visualizator.d3d_Device);
            cuda = Visualizator.Cuda;
            //CUDADriver.cuInit(0);
            Util.CheckCUDA();

        }

        public void Integrate(float dt)
        {
            Debug.Assert(Pos.Length >= ParticleCount);
            Debug.Assert(PrevPos.Length >= ParticleCount);

            // Calculamos el tamaño del grid (es decir, el numero de bloques)
            // Si la cantidad de particulas no es un multiplo de la dimension del
            // bloque, vamos a necesitar un bloque mas.
            int gridWidth = ParticleCount / BLOCK_DIM;
            gridWidth += ParticleCount % BLOCK_DIM == 0 ? 0 : 1;


            cuda.Launch(integrateFunction, gridWidth, 1);

            cuda.CopyDeviceToHost<Float4>(d_Pos, pos);
            cuda.CopyDeviceToHost<Float4>(d_PrevPos, prevPos);
        }

        private CUDA cuda;
        private CUfunction integrateFunction;

        private CUdeviceptr d_Pos;
        private CUdeviceptr d_PrevPos;

        private const int BLOCK_DIM = 256;

        

        public void Initialize()
        {
            //const int size_x = 256;
            //const int size_y = 4096;
            //const int mem_size = sizeof(float) * size_x * size_y;

            //float[] h_idata = new float[size_x * size_y];
            //for (int i = 0; i < h_idata.Length; i++)
            //{
            //    h_idata[i] = (float)i;
            //}

            // allocate device memory
            // copy host memory to device
            //CUdeviceptr d_idata = cuda.CopyHostToDevice<float>(h_idata);
            //CUdeviceptr d_odata = cuda.Allocate<float>(h_idata);

            //d_PosX = cuda.CopyHostToDevice<float>(posX);
            //d_PosY = cuda.CopyHostToDevice<float>(posY);
            //d_PosZ = cuda.CopyHostToDevice<float>(posZ);
            //d_PrevPosX = cuda.CopyHostToDevice<float>(prevPosX);
            //d_PrevPosY = cuda.CopyHostToDevice<float>(prevPosY);
            //d_PrevPosZ = cuda.CopyHostToDevice<float>(prevPosZ);
            cudaError error;
            //int cudaDevice;

            //CUDADriver.cuInit(0);

            //CUdevice cudaDevice = new CUdevice();
            //CUResult res = CUDADriver.cuDeviceGet(ref cudaDevice, 0);
            //CheckCudaError(res);

            //CUcontext cudaContext = default(CUcontext);
            
            //res = CUD3DDriver.cuD3D9CtxCreate(out cudaContext, cudaDevice,0, Visualizator.d3d_Device);
            //res = CUDADriver.cuCtxCreate(ref cudaContext, 0, cudaDevice);
            //res = CUD3DDriver.cuD3D9CtxCreate(cuCtxCreate(ref cudaContext, 0, cudaDevice);
            //CheckCudaError(res);

            //cuda.AttachContext(cudaContext);

            //res = CUD3D10Driver.cuD3D10RegisterResource(vertexBuffer.ComPointer, CUD3D10RegisterFlags.None);
            //res = CUD3DDriver.cuD3D9RegisterVertexBuffer(Visualizator.d3d_VertexBuffer);
            //error = CUD3DRuntime.cudaD3D9SetDirect3DDevice(Visualizator.d3d_Device);

            //error = CUD3DDriver.cuD3D9CtxCreate(
            //error = CUD3DRuntime.cudaD3D9Begin(Visualizator.d3d_Device);
            //error = CUD3DRuntime.cudaD3D9GetDevice(out cudaDevice, GraphicsAdapter.DefaultAdapter.DeviceName);
            //error = CUD3DRuntime.cudaD3D9GetDevice(out cudaDevice, GraphicsAdapter.DefaultAdapter.DeviceName);
            //error = CUD3DRuntime.cudaD3D9RegisterResource(Visualizator.d3d_VertexBuffer, cudaD3D9RegisterFlags.None);
            //error = CUD3DRuntime.cudaD3D9RegisterVertexBuffer(Visualizator.d3d_VertexBuffer);
            //cuda.AttachContext(cudaContext);
            //CUDADriver.
            //error = CUD3DRuntime.cudaD3D9RegisterResource(Visualizator.d3d_VertexBuffer, cudaD3D9RegisterFlags.None);

            // load module
            cuda.LoadModule(Path.Combine(Environment.CurrentDirectory, "VerletIntegrator.cubin"));
            integrateFunction = cuda.GetModuleFunction("Integrate");

            // setup execution parameters
            cuda.SetFunctionBlockShape(integrateFunction, BLOCK_DIM, 1, 1);
            cuda.SetParameter(integrateFunction, IntPtr.Size * 0, (uint)d_Pos.Pointer);
            cuda.SetParameter(integrateFunction, IntPtr.Size * 1, (uint)d_PrevPos.Pointer);
            cuda.SetParameter(integrateFunction, IntPtr.Size * 2, (uint)ParticleCount);
            cuda.SetParameterSize(integrateFunction, (uint)(IntPtr.Size * 2 + 4));
            //cuda.SetParameterSize(integrateFunction, (uint)(0));

            // Warmup so we don't time CUDA startup
            cuda.Launch(integrateFunction, 1, 1);
            Util.CheckCUDA();

            //int numIterations = 1;

            //Console.WriteLine("Integrating by GPU");
            //CUevent start = cuda.CreateEvent();
            //CUevent end = cuda.CreateEvent();
            //cuda.RecordEvent(start);
            //for (int i = 0; i < numIterations; i++)
            //{
            //    cuda.Launch(integrateFunction, ParticleCount / BLOCK_DIM, 1);
            //}
            //cuda.SynchronizeContext();
            //cuda.RecordEvent(end);
            //float naiveTime = cuda.ElapsedTime(start, end);

            //Console.WriteLine("Naive transpose average time:     {0} ms\n", naiveTime / numIterations);
            //Console.WriteLine("Optimized transpose average time:     {0} ms\n", optimizedTime / numIterations);

            //float[] h_odata = new float[size_x * size_y];
            //cuda.CopyDeviceToHost<float>(d_odata, h_odata);

            //cuda.CopyDeviceToHost<float>(d_PosX, posX);
            //cuda.CopyDeviceToHost<float>(d_PosY, posY);
            //cuda.CopyDeviceToHost<float>(d_PosZ, posZ);
            //cuda.CopyDeviceToHost<float>(d_PrevPosX, prevPosX);
            //cuda.CopyDeviceToHost<float>(d_PrevPosY, prevPosY);
            //cuda.CopyDeviceToHost<float>(d_PrevPosZ, prevPosZ);

            //float[] reference = new float[size_x * size_y];
            //computeGold(reference, h_idata, size_x, size_y);

            //bool res = CompareF(reference, h_odata, size_x * size_y);
            //Console.WriteLine("Test {0}", res == true ? "PASSED" : "FAILED");

            //cuda.Free(d_idata);
            //cuda.Free(d_odata);

        }

        private static void CheckCudaError(CUResult err)
        {
            if (err != CUResult.Success)
            {
                throw new Exception(err.ToString());
            }
        }
    }
}
